"""
审计日志
"""

import json
import traceback
from functools import wraps

from starlette.responses import Response

import utils.enum_utils as enum
from components.logger import log
from models.other.ctx import RequestContext
from models.po import AuditLogPO
from my_typing import *
from utils.data_utils.special import ObjectIdStr
from utils.exception_utils import BusinessError

T = TypeVar("T", bound=Response)


class AuditUtils:
    """ 审计日志工具类 """

    @staticmethod
    def parse_response_data(response: Response) -> dict:
        """ 解析响应数据 """

        try:
            if isinstance(getattr(response, "body", None), bytes):
                return json.loads(response.body.decode())

        except Exception as _:  # noqa: ignore[broad exception]
            ...

        return {}

    @staticmethod
    def convert_to_object_id_list(value: Any) -> list[ObjectIdStr]:
        """ 将值转换为 ObjectId 列表 """

        if not value:
            return []

        # 如果不是列表，转换为列表
        if not isinstance(value, (list, tuple)):
            value = [value]

        return [
            ObjectIdStr(_)
            for _ in value
            if ObjectIdStr.is_valid(_)
        ]

    @classmethod
    def parse_po_id_list(
        cls,
        request_params: dict,
        response_data: dict,
        po_id: StrOrNone = None,
    ) -> list[ObjectIdStr]:
        """ 解析 PO ID 列表 """

        if not po_id:
            return []

        # 单层，直接从请求参数获取数据
        if "." not in po_id:
            return cls.convert_to_object_id_list(request_params.get(po_id))

        # 多层，从请求体/响应体获取数据
        param_list: ListStr = po_id.split(".")
        first: str = param_list.pop(0)

        if first not in ("request", "response"):
            return []

        # 从请求体获取数据
        if (
            first == "request"
            and "request" in request_params
        ):
            current = request_params.get("request")

            for param in param_list:
                if current is None:
                    return []

                current = getattr(current, param, None)

            return cls.convert_to_object_id_list(current)

        # 从响应体获取数据
        if (
            first == "response"
            and response_data
        ):
            try:
                # 获取嵌套数据
                current = response_data

                for param in param_list:
                    if not current:
                        return []

                    current = (
                        current.get(param)
                        if isinstance(current, dict)
                        else None
                    )

                    # 如果当前值是列表，且不是最后一个参数
                    if (
                        isinstance(current, list)
                        and param != param_list[-1]
                    ):
                        # 从列表中每个元素获取后续的字段
                        result = []
                        remaining_params = param_list[param_list.index(param) + 1:]

                        for item in current:
                            temp = item

                            for next_param in remaining_params:
                                if not isinstance(temp, dict):
                                    break

                                temp = temp.get(next_param)

                            if temp:
                                result.append(temp)

                        return cls.convert_to_object_id_list(result)

                return cls.convert_to_object_id_list(current)

            except Exception as _:  # noqa: ignore[broad exception]
                return []

        return []

    @staticmethod
    def fill_user_id(
        request_log_po: AuditLogPO,
        fill_user_id: bool,
    ) -> AuditLogPO:
        """ 填充用户 ID """

        # 无需填充 user_id 或 已有 user_id，直接 return
        if (
            not fill_user_id
            or request_log_po.user_id
        ):
            return request_log_po

        if len(request_log_po.po_id_list) == 1:
            request_log_po.user_id = request_log_po.po_id_list[0]

        return request_log_po


def audit(
    model: enum.Model,
    operation: enum.ModelOperation,
    *,
    po_id: StrOrNone = None,
    fill_user_id: bool = False,
) -> Callable[[Callable[..., Awaitable[T]]], Callable[..., Awaitable[T]]]:
    """
    审计日志装饰器

    Args:
        model: 模型
        operation: 操作类型
        po_id: PO ID 路径，支持嵌套，如：
            "id"、"route_id"、"request.file_id_list"、
            "response.data.user_id"、"response.data.data_list.route_id"
        fill_user_id: 是否填充用户 ID（仅用于用户注册、登录）
    """

    if (
        po_id
        and not isinstance(po_id, str)
    ):
        raise ValueError("po_id must be a string")

    def decorator(async_func: Callable[..., Awaitable[Response]]) -> Callable[..., Awaitable[Response]]:
        @wraps(async_func)
        async def wrapper(**kwargs) -> Response | None:  # 移除了 *args 参数，因为 FastAPI 只使用关键字参数
            from biz import audit_log_biz
            from components.global_param import get_ctx

            ctx: RequestContext = get_ctx()
            response: Response | None = None
            audit_info: DictStrAny = {
                "ip": ctx.request_ip,
                "model": model,
                "operation": operation,
                "po_id_list": [],
                "request_id": ctx.request_id,
                "trace_id": ctx.trace_id,
                "exception": False,
                "status_code": enum.HTTPStatusCode.HTTP_200_OK,
                "user_id": ctx.user_id,
            }

            try:
                response = await async_func(**kwargs)

            except BusinessError:  # 业务异常，无需打印异常的日志
                audit_info["status_code"] = enum.HTTPStatusCode.HTTP_400_BAD_REQUEST
                raise

            except Exception as _:  # noqa: ignore[broad exception]
                audit_info["exception"] = True
                audit_info["status_code"] = enum.HTTPStatusCode.HTTP_500_INTERNAL_SERVER_ERROR
                log.error(f"[审计日志解析] Exception: {traceback.format_exc()}")
                raise

            finally:
                response_data: dict = AuditUtils.parse_response_data(response)
                audit_info["po_id_list"] = AuditUtils.parse_po_id_list(kwargs, response_data, po_id)
                audit_info["status_code"] = response_data.get("code", audit_info["status_code"])
                request_log_po: AuditLogPO = AuditLogPO(**audit_info)
                request_log_po = AuditUtils.fill_user_id(request_log_po, fill_user_id)  # 填充用户 ID
                audit_log_biz.AuditLogSetter.async_create_audit_log(request_log_po)

            return response

        return wrapper

    return decorator
